package messaging;

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import model.Software;
import workerUIs.StepFrame;

/**
 * Default {@link IMessagingHandler} implementation. 
 * Contains logic for Point to Point JMS messaging.
 * Can handle multiple input and output points. 
 * Input points always synced (all input point must supply input object).
 * Output handling can be configured for specific destination handling 
 * (send to output by name, or send to all).   
 * @author Mark Czotter
 *
 */
public class SoftwareDevelopmentMessagingHandler implements IMessagingHandler<Software> {

	private Logger logger = Logger.getLogger(SoftwareDevelopmentMessagingHandler.class.getCanonicalName());
	private QueueConnection queueConnection;
	private QueueSession queueSession;
	private List<Queue> inputQueues = new ArrayList<Queue>();
	private List<Queue> outputQueues = new ArrayList<Queue>();
	private final StepFrame stepFrame;
	private Map<String, List<Software>> jmsQueueToLocalQueue = new HashMap<String, List<Software>>();
	private List<Integer> readySoftwareIds = new ArrayList<Integer>();
	private List<Queue> targetQueues;

	public SoftwareDevelopmentMessagingHandler(StepFrame stepFrame, List<String> inputQueueNames, List<String> outputQueueNames) throws NamingException, JMSException {
		this.stepFrame = stepFrame;
		initPTP(inputQueueNames, outputQueueNames);
		if (this.stepFrame != null) {
			this.stepFrame.addWindowListener(new WindowAdapter() {
				@Override
				public void windowClosing(WindowEvent arg0) {
					try {
						close();
					} catch (JMSException e) {
						logger.log(Level.SEVERE, "Exception during close", e);
					}
				}
			});			
		}
		this.targetQueues = new ArrayList<Queue>(this.outputQueues);
	}
	
	protected void initPTP(List<String> inputQueueNames, List<String> outputQueueNames) throws NamingException, JMSException {
		InitialContext context = new InitialContext();
        Object tmp = context.lookup("ConnectionFactory");
        QueueConnectionFactory qcf = (QueueConnectionFactory) tmp;
        queueConnection = qcf.createQueueConnection();
        queueSession = queueConnection.createQueueSession(false,
                                          QueueSession.AUTO_ACKNOWLEDGE);
        queueConnection.start();
        if (inputQueueNames != null) {
        	for (String queueName : inputQueueNames) {
        		Queue queue = (Queue) context.lookup("queue/" + queueName);
            	QueueReceiver receiver = queueSession.createReceiver(queue);
            	receiver.setMessageListener(this);
            	this.inputQueues.add(queue);
            	jmsQueueToLocalQueue.put(queueName, new ArrayList<Software>());
        	}
        }
        if (outputQueueNames != null) {
        	for (String queueName : outputQueueNames) {
        		Queue queue = (Queue) context.lookup("queue/" + queueName);
            	this.outputQueues.add(queue);
        	}
        }
	}

	@Override
	public void put(Software t) throws JMSException {
		for (Queue queue : this.targetQueues) {
			QueueSender sender = queueSession.createSender(queue);
			ObjectMessage softwareMessage = queueSession.createObjectMessage(t);
			sender.send(softwareMessage);
			sender.close();	
		}
	}

	@Override
	public Software get() throws JMSException {
		if (this.stepFrame != null) {
			this.stepFrame.getNextButton().setEnabled(false);
		}
		return mergeSoftware();
	}

	private Software mergeSoftware() {
		if (this.readySoftwareIds.isEmpty()) {
			return null;
		}
		// select an id and the corresponding softwares from the local queues
		int currentId = this.readySoftwareIds.remove(0);
		List<Software> currentSoftwares = new ArrayList<Software>();
		for (List<Software> swList : this.jmsQueueToLocalQueue.values()) {
			Software removeable = null;
			for (Software software : swList) {
				if (software.getId() == currentId) {
					currentSoftwares.add(software);
					removeable = software;
				}
			}
			if (removeable == null) {
				throw new RuntimeException("Removeable software not found!");
			}
			swList.remove(removeable);
		}
		if (this.inputQueues.size() != currentSoftwares.size()) {
			throw new RuntimeException("During merge, the input queues size not equals the mergeable softwares size!");
		}
		// do the merge, very dummy merge, only works properly with the integration step
		Software mergedSoftware = new Software(currentId);
		for (Software software : currentSoftwares) {
			mergedSoftware.setName(software.getName());
			mergedSoftware.setSystemDesignPlan(software.getSystemDesignPlan());
			mergedSoftware.getComponents().addAll(software.getComponents());
			mergedSoftware.setIntegrated(software.isIntegrated());
			mergedSoftware.setVersion(software.getVersion());
		}
		// allow the user to step the process
		if (!this.readySoftwareIds.isEmpty() && this.stepFrame != null) {
			this.stepFrame.getNextButton().setEnabled(true);
		}
		return mergedSoftware;
	}

	@Override
	public void close() throws JMSException {
		if (this.queueConnection != null) {
			this.queueConnection.stop();
			this.queueConnection.close();
			this.queueSession.close();
		}
	}

	@Override
	public void onMessage(Message msg) {
		try {
			Queue queue = (Queue) msg.getJMSDestination();
			List<Software> queueSoftwares = this.jmsQueueToLocalQueue.get(queue.getQueueName());
			Software sw = (Software) ((ObjectMessage)msg).getObject();
			queueSoftwares.add(sw);
			if (this.stepFrame != null) {
				int currentId = sw.getId();
				boolean isAllListContainsThisId = true;
				for (List<Software> softwareList : this.jmsQueueToLocalQueue.values()) {
					boolean listContainsId = false;
					for (Software software : softwareList) {
						if (software.getId() == currentId) {
							listContainsId = true;
						}
					}
					if (!listContainsId) {
						isAllListContainsThisId = false;
						break;
					}
				}
				if (isAllListContainsThisId) {
					this.readySoftwareIds.add(currentId);
					this.stepFrame.getTextArea().setText(this.stepFrame.getTextArea().getText() + "\n-----------\nMesssage Arrived!\n-----------\n");
					this.stepFrame.getNextButton().setEnabled(true);
				}
			}
		} catch (Exception e) {
			logger.log(Level.SEVERE, "Exception during onMessage!", e);
		}
	}
	
	public void setTargetQueue(String targetQueueName) {
		List<String> targetQueueNames = new ArrayList<String>();
		targetQueueNames.add(targetQueueName);
		setTargetQueues(targetQueueNames);
	}
	
	public void setTargetQueues(List<String> targetQueueNames) {
		try {
			this.targetQueues = new ArrayList<Queue>();
			for (String queueName : targetQueueNames) {
				for (Queue queue : outputQueues) {
					if (queue.getQueueName().equals(queueName)) {
						this.targetQueues.add(queue);
					}
				}
			}
			if (this.targetQueues.size() != targetQueueNames.size()) {
				logger.log(Level.SEVERE, "Some queue not found!");
			}
		} catch (JMSException e) {
			logger.log(Level.SEVERE, "Exception during target queue set!", e);
		}
	}

}
